// Copyright 2016 Proyectos y Sistemas de Mantenimiento SL (eProsima).
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------
// Modification history:
// feature: authentication-free shared key and token derivation between clients
// feature: change E2E packet format
// feature: support auth token
// feature: E2E New Requirement Development
// feature: Development of new requirements for E2E functionality
// ------------------------------------------------------------------

#include <edds/rtps/messages/RTPSMessageCreator.h>

#include <edds/rtps/messages/CDRMessage.h>

#include <map>
#include <elog/Log.hpp>
#include <edds/dds/core/policy/ParameterList.hpp>

using namespace evbs::ertps;
using ParameterList = evbs::edds::dds::ParameterList;

namespace evbs {
namespace ertps {
namespace rtps {

bool RTPSMessageCreator::addHeader(CDRMessage_t* msg, const GuidPrefix_t& guidPrefix) {
    (void)CDRMessage::addOctet(msg, 'R');
    (void)CDRMessage::addOctet(msg, 'T');
    (void)CDRMessage::addOctet(msg, 'P');
    (void)CDRMessage::addOctet(msg, 'S');

    (void)CDRMessage::addOctet(msg, c_ProtocolVersion.m_major);
    (void)CDRMessage::addOctet(msg, c_ProtocolVersion.m_minor);

    (void)CDRMessage::addOctet(msg, c_VendorId_vbs[0]);
    (void)CDRMessage::addOctet(msg, c_VendorId_vbs[1]);

    (void)CDRMessage::addData(msg, guidPrefix.value, 12U);
    msg->length = msg->pos;

    return true;
}

bool RTPSMessageCreator::addCustomContent(CDRMessage_t* msg, const octet* content, const size_t contentSize) {
    (void)CDRMessage::addData(msg, content, static_cast<uint32_t>(contentSize));
    msg->length = msg->pos;

    return true;
}

bool RTPSMessageCreator::addSubmessageHeader(CDRMessage_t* msg, octet id, octet flags, uint16_t size) {
#if EDDS_IS_BIG_ENDIAN_TARGET
    msg->msg_endian = BIGEND;
#else
    flags = flags | BIT(0);
    msg->msg_endian = LITTLEEND;
#endif  // if EDDS_IS_BIG_ENDIAN_TARGET

    (void)CDRMessage::addOctet(msg, id);
    (void)CDRMessage::addOctet(msg, flags);
    (void)CDRMessage::addUInt16(msg, size);
    msg->length = msg->pos;

    return true;
}

#if HAVE_SECURITY
bool RTPSMessageCreator::addSubmessageAuthToken(CDRMessage_t* msg, uint32_t token) {
    octet flags = 0x0U;
#if EDDS_IS_BIG_ENDIAN_TARGET
    msg->msg_endian = BIGEND;
#else
    flags = flags | BIT(0);
    msg->msg_endian = LITTLEEND;
#endif  // if EDDS_IS_BIG_ENDIAN_TARGET

    (void)CDRMessage::addOctet(msg, AUTH_TOKEN);
    (void)CDRMessage::addOctet(msg, flags);
    (void)CDRMessage::addUInt16(msg, 4U);
    (void)CDRMessage::addUInt32(msg, token);

    return true;
}

bool RTPSMessageCreator::addSubmessageSharedKey(CDRMessage_t* msg, const std::vector<uint8_t>& shared_key) {
    octet flags = 0x0U;
#if EDDS_IS_BIG_ENDIAN_TARGET
    msg->msg_endian = BIGEND;
#else
    flags = flags | BIT(0);
    msg->msg_endian = LITTLEEND;
#endif  // if EDDS_IS_BIG_ENDIAN_TARGET

    (void)CDRMessage::addOctet(msg, SHARED_KEY);
    (void)CDRMessage::addOctet(msg, flags);
    (void)CDRMessage::addUInt16(msg, shared_key.size());
    logDebug(SECURITY, "sharedkey size:" << shared_key.size());
    (void)CDRMessage::addData(msg, shared_key.data(), shared_key.size());

    return true;
}
#endif

bool RTPSMessageCreator::addSubmessageInfoTS(CDRMessage_t* msg, const Time_t& time, bool invalidateFlag) {
    octet flags = 0x0U;
#if EDDS_IS_BIG_ENDIAN_TARGET
    msg->msg_endian = BIGEND;
#else
    flags = flags | BIT(0);
    msg->msg_endian = LITTLEEND;
#endif  // if EDDS_IS_BIG_ENDIAN_TARGET

    (void)CDRMessage::addOctet(msg, INFO_TS);
    if (invalidateFlag) {
        flags = flags | BIT(1);
        (void)CDRMessage::addOctet(msg, flags);
        (void)CDRMessage::addUInt16(msg, 0U);
    } else {
        (void)CDRMessage::addOctet(msg, flags);
        (void)CDRMessage::addUInt16(msg, 8U);
        (void)CDRMessage::addInt32(msg, time.seconds());
        (void)CDRMessage::addUInt32(msg, time.fraction());
    }

    return true;
}

bool RTPSMessageCreator::addSubmessageInfoSRC(CDRMessage_t* msg, const ProtocolVersion_t& version,
                                              const VendorId_t& vendorId, const GuidPrefix_t& guidPrefix) {
    octet flags = 0x0U;
    uint16_t size = 20U;
#if EDDS_IS_BIG_ENDIAN_TARGET
    msg->msg_endian = BIGEND;
#else
    flags = flags | BIT(0);
    msg->msg_endian = LITTLEEND;
#endif  // if EDDS_IS_BIG_ENDIAN_TARGET

    (void)CDRMessage::addOctet(msg, INFO_SRC);
    (void)CDRMessage::addOctet(msg, flags);
    (void)CDRMessage::addUInt16(msg, size);
    (void)CDRMessage::addUInt32(msg, 0U);
    (void)CDRMessage::addOctet(msg, version.m_major);
    (void)CDRMessage::addOctet(msg, version.m_minor);

    (void)CDRMessage::addOctet(msg, vendorId[0]);
    (void)CDRMessage::addOctet(msg, vendorId[1]);

    (void)CDRMessage::addData(msg, guidPrefix.value, 12U);

    return true;
}

bool RTPSMessageCreator::addSubmessageInfoDST(CDRMessage_t* msg, const GuidPrefix_t& guidP) {
    octet flags = 0x0U;
    uint16_t size = 12U;
#if EDDS_IS_BIG_ENDIAN_TARGET
    msg->msg_endian = BIGEND;
#else
    flags = flags | BIT(0);
    msg->msg_endian = LITTLEEND;
#endif  // if EDDS_IS_BIG_ENDIAN_TARGET

    (void)CDRMessage::addOctet(msg, INFO_DST);
    (void)CDRMessage::addOctet(msg, flags);
    (void)CDRMessage::addUInt16(msg, size);
    (void)CDRMessage::addData(msg, guidP.value, 12U);

    return true;
}

bool RTPSMessageCreator::addSubmessageInfoTS_Now(CDRMessage_t* msg, bool invalidateFlag) {
    Time_t time_now;
    Time_t::now(time_now);
    return RTPSMessageCreator::addSubmessageInfoTS(msg, time_now, invalidateFlag);
}

bool RTPSMessageCreator::addSubmessageInfoE2E(CDRMessage_t* msg, const EntityId_t& readerId, CacheChange_t& change) {
    octet flags = 0x0U;
    uint32_t length = 0U;
    uint32_t crc = 0U;
    uint32_t reader_id = 0U;

#if EDDS_IS_BIG_ENDIAN_TARGET
    msg->msg_endian = BIGEND;
#else
    flags = flags | BIT(0);
    msg->msg_endian = LITTLEEND;
#endif  // if EDDS_IS_BIG_ENDIAN_TARGET
    length = change.serializedPayload.length + ((4U - change.serializedPayload.length % 4U) & 3U);
    memcpy((void*)&reader_id, readerId.value, 4);

    change.isRetrans = false;

#ifdef BATCH_SEND_ENABLE
    if (!change.batch_send) {
        crc = e2eCommon::calculateCRC32P4(change.serializedPayload.data, length, readerId, change.writerGUID.entityId,
                                          change.sequenceNumber);
    } else {
        // uint32_t len = 0U;
        // for (auto it = change.batch_changes.begin(); it != change.batch_changes.end(); it++) {
        //     crc = e2eCommon::calculateCRC32P4(crc, (*it)->serializedPayload.data, (*it)->serializedPayload.length,
        //                                       it == change.batch_changes.begin());
        //     len += (*it)->serializedPayload.length;
        // }
        // uint32_t align = (4U - len % 4U) & 3U;
        // octet zero_byte = 0U;
        // for (uint32_t i = 0U; i < align; i++) {
        //     crc = e2eCommon::calculateCRC32P4(crc, &zero_byte, 1U);
        // }
        // crc = e2eCommon::calculateCRC32P4(crc, counter, app_id);
        {
            uint32_t sample_info_len = change.batchSampleCount * RTPSMESSAGE_SAMPLE_INFO_SIZE;
            if (change.serializedPayload.length >= sample_info_len) {
                crc = e2eCommon::calculateCRC32P4(change.serializedPayload.data + sample_info_len,
                                            change.serializedPayload.length - sample_info_len, change.writerGUID.entityId,
                                            change.readerGUID.entityId, change.sequenceNumber);
            }
        }
    }
#else
    crc = e2eCommon::calculateCRC32P4(change.serializedPayload.data, length, readerId, change.writerGUID.entityId,
                                      change.sequenceNumber);
#endif

    (void)CDRMessage::addOctet(msg, INFO_E2E);
    (void)CDRMessage::addOctet(msg, flags);
    (void)CDRMessage::addUInt16(msg, 8U);
    (void)CDRMessage::addUInt32(msg, length);
    (void)CDRMessage::addUInt32(msg, crc);

    return true;
}

}  // namespace rtps
}  // namespace ertps
} /* namespace evbs */

#include <rtps/messages/submessages/DataMsg.hpp>
#include <rtps/messages/submessages/HeartbeatMsg.hpp>
#include <rtps/messages/submessages/AckNackMsg.hpp>
#include <rtps/messages/submessages/GapMsg.hpp>
